Using R in hydrology (EGU2017 short course)

Instructors: Shaun Harrigan, Katie Smith, Berry Boessenkool and Daniel Klotz
Organizer: Berry Boessenkool, PhD student at Potsdam University (Germany)
Contact: Questions and feedback are welcome via berry-b@gmx.de

These slides and all other course materials can be found at
github.com/brry/rhydro
Download the github course repository with all the materials including the datasets and presentation source code.
This is an R Markdown Notebook.
For discussions, please visit the Hydrology in R Facebook group.
Before running the code blocks below, we suggest to get package installation instructions by running:

source("https://raw.githubusercontent.com/brry/rhydro/master/checkpc.R")


Aim and contents of this workshop

We want to:

We can not:

We have prepared:

 

Before we get started, please let us know your current R knowledge level by filling out the short survey at
bit.ly/knowR


top

Report

Good coding practice, report generation (Rstudio, rmarkdown, R notebook)
Daniel Klotz

Introduction

goals

goals

Why I use R

Why I did not use:

equals

equals

Whats great about R:

  library(ggplot2)
  test_data <- mpg
  test_plot <- ggplot(test_data, aes(displ, hwy, colour = class)) + 
    geom_point()
  test_plot

Why I decided to use R:

pipe

pipe

Previously:

  aggregation_function <- function(x) {
    round(mean(x),2)
  }
  mtcars_subset <- subset(mtcars,hp > 100)
  mtcars_aggregated <- aggregate(. ~ cyl, data = mtcars_subset, FUN = aggregation_function)
  car_data1 <- transform(mtcars_aggregated, kpl = mpg*0.4251)
  print(car_data1)

Now:

library(magrittr)
car_data2 <- 
  mtcars %>%
  subset(hp > 100) %>%
  aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%
  transform(kpl = mpg %>% multiply_by(0.4251)) %>%
  print

btw: You can integrate other programming languages with ease. Here an example from Yihui Xie for the use of Fortran in Rmarkdown:

  1. Compile Code:
```r
C Fortran test
      subroutine fexp(n, x)
      double precision x
C  output
      integer n, i
C  input value
      do 10 i=1,n
         x=dexp(dcos(dsin(dble(float(i)))))
  10  continue
      return
      end
```
  1. Run Code:
```r
res = .Fortran("fexp", n=100000L, x=0)
str(res)
```

Be happy with the result: > ## List of 2 > ## $ n: int 100000 > ## $ x: num 2.72


Markdown

HTML: HyperText Markdown Language

John Gruber:

John Gruber

Comparison: Markdown vs. Latex Comparison

Rstudio provides cheat-sheets with the most important informations about many of their “favorite” packages & software:

Cheat Sheet

Rmarkdown

In Rstudio:

Rmark1

Rmark2

Rmark2

“Native” Formats:

Much more possible if you adress pandoc directly: pandoc

Information in the text can be automatically updated with the rest of the document: [time for coffee

Examples

Small Websites

Cayman Theme

Cayman Theme

Books (blogdown)

bookdown1 bookdown2

Blogs (hugo)

hugo

Presentations

bookdown1

Widgets

cran-gauge

Apps (Shiny)

shiny shiny2 shiny2


top

GIS

Using R as GIS (reading a rainfall shapefile + Kriging, sf + leaflet + mapview + OSMscale)
Berry Boessenkool

Shapefiles

Reading shapefiles with maptools::readShapeSpatial and rgdal::readOGR is obsolete.
Instead, use sf::st_read. sf is on CRAN since oct 2016.
Main reaction when using sf: “Wow, that is fast!”
Download the shapefile or better: download the whole github course repository

rain <- sf::st_read("data/PrecBrandenburg/niederschlag.shp")
Reading layer `niederschlag' from data source `/home/berry/Dropbox/R/rhydro/presentations/data/PrecBrandenburg/niederschlag.shp' using driver `ESRI Shapefile'
converted into: POLYGON
Simple feature collection with 277 features and 1 field
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 3250175 ymin: 5690642 xmax: 3483631 ymax: 5932731
epsg (SRID):    NA
proj4string:    +proj=tmerc +lat_0=0 +lon_0=15 +k=0.9996 +x_0=3500000 +y_0=0 +ellps=GRS80 +units=m +no_defs

Central points of rainfall Thiessen polygons

centroids <- sf::st_centroid(rain)
centroids <- sf::st_coordinates(centroids)
centroids <- as.data.frame(centroids)

top

Plotting, maps

Static plot:

plot(rain[,1])

Static map:

prj <- sf::st_crs(rain)$proj4string
cent_ll <- OSMscale::projectPoints(Y,X, data=centroids, to=OSMscale::pll(), from=prj)
#map_static <- OSMscale::pointsMap(y,x, cent_ll, fx=0.08, type="maptoolkit-topo", zoom=6)
#save(map_static, file="data/map_static.Rdata")
load("data/map_static.Rdata")
OSMscale::pointsMap(y,x, cent_ll, map=map_static)
Done. Now plotting...

Interactive map:

library(leaflet)
cent_ll$info <- paste0(sample(letters,nrow(cent_ll),TRUE), ", ", round(cent_ll$x,2), 
                       ", ", round(cent_ll$y,2))
leaflet(cent_ll) %>% addTiles() %>% addCircleMarkers(lng=~x, lat=~y, popup=~info)

Interactive map of shapefile:

# make sure to have installed the development version of mapview: 
# devtools::install_github("environmentalinformatics-marburg/mapview", ref = "develop")
library(berryFunctions) # classify, seqPal
col <- seqPal(n=100, colors=c("red","yellow","blue"))[classify(rain$P1)$index]
mapview::mapview(rain, col.regions=col)

top

Kriging

Plot original points colored by third dimension:

pcol <- colorRampPalette(c("red","yellow","blue"))(50)
x <- centroids$X # use cent_ll$x for projected data
y <- centroids$Y
berryFunctions::colPoints(x, y, rain$P1, add=FALSE, col=pcol)

Calculate the variogram and fit a semivariance curve

library(geoR)
--------------------------------------------------------------
 Analysis of Geostatistical Data
 For an Introduction to geoR go to http://www.leg.ufpr.br/geoR
 geoR version 1.7-5.2 (built on 2016-05-02) is now loaded
--------------------------------------------------------------
geoprec <- as.geodata(cbind(x,y,rain$P1))
vario <- variog(geoprec, max.dist=130000) # other maxdist for lat-lon data
variog: computing omnidirectional variogram
fit <- variofit(vario)
variofit: covariance model used is matern 
variofit: weights used: npairs 
variofit: minimisation function used: optim 
initial values not provided - running the default search
variofit: searching for best initial value ... selected values:
              sigmasq   phi        tausq kappa
initial.value "1325.81" "19999.05" "0"   "0.5"
status        "est"     "est"      "est" "fix"
loss value: 104819060.429841 
plot(vario)
lines(fit)

Determine a useful resolution (keep in mind that computing time rises exponentially with grid size)

# distance to closest other point:
d <- sapply(1:length(x), function(i)
            min(berryFunctions::distance(x[i], y[i], x[-i], y[-i])) )
# for lat-long data use (2017-Apr only available in github version of OSMscale)
# d <- OSMscale::maxEarthDist(y,x, data=cent_ll, fun=min)
hist(d/1000, breaks=20, main="distance to closest gauge [km]")

mean(d/1000) # 8 km
[1] 8.165713

Perform kriging on a grid with that resolution

res <- 1000 # 1 km, since stations are 8 km apart on average
grid <- expand.grid(seq(min(x),max(x),res),
                    seq(min(y),max(y),res))
krico <- krige.control(type.krige="OK", obj.model=fit)
#krobj <- krige.conv(geoprec, loc=grid, krige=krico)
#save(krobj, file="data/krobj.Rdata")
load("data/krobj.Rdata") # line above is too slow for recreation each time

Plot the interpolated values with image or an equivalent function (see Rclick 4.15) and add contour lines.

par(mar=c(0,3,0,3))
geoR:::image.kriging(krobj, col=pcol)
colPoints(x, y, rain$P1, col=pcol, legargs=list(horiz=F, title="Prec",y1=0.1,x1=0.9))
points(x,y)
plot(rain, col=NA, add=TRUE)


top

Discharge

River discharge time-series visualisation and extreme value statistics (animation + extremeStat)
Berry Boessenkool

Read, plot and aggregate data

Datasets from http://nrfa.ceh.ac.uk/data/station/meanflow/39072
Download discharge1, discharge2_xxx, discharge3_xxx, or better: download the whole github course repository

Read and transform data

Q <- read.table("data/discharge39072.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q) <- c("date","Royal_Windsor_Park")
Q$date <- as.Date(Q$date, format="%Y-%m-%d")

Examine data

head(Q)
str(Q)
'data.frame':   13222 obs. of  2 variables:
 $ date              : Date, format: "1979-07-20" "1979-07-21" "1979-07-22" ...
 $ Royal_Windsor_Park: num  33.4 32.5 33.1 30.6 30.1 ...

Simple time series plot

plot(Q, type="l", col="blue")

Publication-ready graphics

png("DischargeVis.png", width=20, height=10, units="cm", res=500)
#pdf("DischargeVis.pdf", width=20/2.5, height=10/2.5) # vector graph
par(mar=c(3.5,3.5,2.5,0.2), mgp=c(2.3,0.7,0), las=1)
plot(Q, type="l", col="blue", main="NRFA: Thames\nRoyal Windsor Park",
     xlab="Date", ylab="Discharge  [m\U{00B3}/s]")
dev.off()
null device 
          1 

Annual maxima, German hydrological year split at Oct 31

Q$hydyear <- as.numeric(format(Q$date+61, "%Y"))
annmax <- tapply(Q$Royal_Windsor_Park, Q$hydyear, max, na.rm=TRUE)
annmax <- annmax[-1]
hydyear <- as.numeric(names(annmax)) 
plot(hydyear, annmax, type="o", las=1)

Extreme value statistics

library(extremeStat)
# Loaded extremeStat 1.3.1 (2017-02-16). Package restructured since 0.6.0 (2016-12-13).
# Computing functions don't plot anymore and some are renamed. See help('extremeStat-deprecated')
dlf <- distLfit(annmax)
Note in distLfit: dat was not a vector.
distLfit execution took 1 seconds.
plotLfit(dlf)

plotLfit(dlf, cdf=TRUE)

dle <- distLextreme(dlf=dlf, RPs=c(5,10,50,100), gpd=FALSE)
plotLextreme(dle)

Logarithmic plot with many fitted distribution functions

plotLextreme(dle, nbest=16, log=TRUE)

Return values (discharge estimates for given return periods)

dlf$returnlev
NULL

In reality, please use non-stationary EVS!

Uncertainty band for Wakeby distribution fit estimate

dle_boot <- distLexBoot(dle, n=10, nbest=1)

   |+++++                                             | 10% ~01s          
   |++++++++++                                        | 20% ~00s          
   |+++++++++++++++                                   | 30% ~00s          
   |++++++++++++++++++++                              | 40% ~00s          
   |+++++++++++++++++++++++++                         | 50% ~00s          
   |++++++++++++++++++++++++++++++                    | 60% ~00s          
   |+++++++++++++++++++++++++++++++++++               | 70% ~00s          
   |++++++++++++++++++++++++++++++++++++++++         | 80% ~00s          
   |+++++++++++++++++++++++++++++++++++++++++++++    | 90% ~00s          
   |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed = 01s
plotLexBoot(dle_boot, distcol="green")

More details in the package vignette

vignette("extremeStat")

Animated movie

Read data from several discharge stations

if(FALSE){
Q2 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q2) <- c("date","dummy1");  Q2$date <- as.Date(Q2$date, format="%Y-%m-%d")
Q3 <- read.table("data/discharge_____.csv", skip=19, header=TRUE, sep=",", fill=TRUE)[,1:2]
colnames(Q3) <- c("date","dummy2");  Q3$date <- as.Date(Q3$date, format="%Y-%m-%d")
}
Q2 <- Q ; Q2[,2] <- Q2[,2] + 100 ; Q2 <- Q2[-(1:100),]
Q3 <- Q ; Q3[,2] <- Q3[,2] -  50 ; Q3 <- head(Q3, -365*3)
colnames(Q2)[2] <- "dummy1"; colnames(Q3)[2] <- "dummy2"

Merge datasets

dis <- Reduce(function(...) merge(..., all=T), list(Q,Q2,Q3))

Code to generate one movie slice

library(berryFunctions) # for lim0, monthAxis, textField, etc
scene <- function(i, vlcnote=TRUE, cex=1.2)
{
 sel <- 0:120
 dis2 <- dis[i + sel, ]
 stat <- c("Royal_Windsor_Park", "dummy1", "dummy2")
 col <- c("red", "blue", "orange")
 names(col) <- stat
 plot(dis2$date,dis2$Royal_Windsor_Park, type="n", ylim=lim0(500),  las=1, 
      xaxt="n", yaxt="n", cex.lab=cex, xlab="", 
      ylab="Discharge  [m\U{00B3}/s]", xaxs="i")
 axis(2, cex.axis=cex, las=1)
 Sys.setlocale("LC_TIME", "C")
 monthAxis(midmonth=TRUE, format="%b\n%Y", cex=cex, mgp=c(3,3.5,0))
 abline(h=1:6*100, v=dis2$date[format(dis2$date,"%d")=="01"], col=8)
 for(s in stat) lines(dis2$date, dis2[,s], lwd=4, col=col[s])
 xi <- seqR(sel,len=length(stat)+2)[-1]
 xi <- head(xi, -1)
 textField(dis2$date[xi], diag(as.matrix(dis2[xi,stat])), stat, cex=cex, col=col)
 box()
 if(vlcnote) mtext("VLC: 'e': single frame forward\n'SHIFT+LEFT': few seconds back",
                   side=3, line=-9, outer=TRUE, adj=0.95, cex=cex)
}
par(mar=c(5,5,0.5,0.5), mgp=c(3,0.7,0))
scene(150)

Actual movie

library(animation)
saveVideo({par(mar=c(6,8,1,1), mgp=c(5.5,0.7,0))
 dummy <- pbsapply(seq(0, by=3, len=50), scene, cex=2); rm(dummy)
}, video.name="Qmovie.mp4", interval=0.1, ffmpeg="/usr/bin/ffmpeg", 
ani.width=7*200, ani.height=5*200)

Open video in default mp4player


top

Hydmod

Hydrological modelling with airGR
Katie Smith (Centre for Ecology & Hydrology) k.a.smith@ceh.ac.uk

This is an demonstration of how to use the airGR package of hydrological models in R, as well as how to plot interactive timeseries graphs with the dygraphs package.

First we need to load some packages

library(xts)
Loading required package: zoo

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric
library(dygraphs)
library(RColorBrewer)

Data

Now we’ll load in some observational flow data from the River Thames (naturalised) in England - with thanks to the National River Flow Archive: http://nrfa.ceh.ac.uk/data/search

observed_data <- read.csv("data/Qobs_390010.csv")
observed_data
observed_data$DATE <- strptime(observed_data$DATE, format = "%d/%m/%Y")

In order to plot this as an interactive dygraph we need to change it to xts format

observed_data_xts <- as.xts(observed_data$Qobs, order.by = observed_data$DATE)
dygraph(observed_data_xts, main="Naturalised Streamflow Observations for the Thames at Kingston")%>%
  dyAxis("y", label="Streamflow (m3/s")%>%
  dyOptions(colors = RColorBrewer::brewer.pal(3, "Set1")[2])%>%
  dyRangeSelector()

Now lets read in some precipitation data - this is from CEH-GEAR: https://data.gov.uk/dataset/gridded-estimates-of-daily-and-monthly-areal-rainfall-for-the-united-kingdom-1890-2012-ceh-gear

precip_data <- read.csv("data/rain_1961_2014_390010.csv")
precip_data

and some potential evapotranspiration data - this is from CHESS-PE:https://data.gov.uk/dataset/climate-hydrology-and-ecology-research-support-system-potential-evapotranspiration-dataset-for-1

PET_data <- read.csv("data/CHESS_PET_1961_2015_390010.csv")
PET_data

Note that our starting dates are not the same as our observational data, so we need to make a dataframe that matches the dates up. There are a lot of ways to do this. First we’ll convert them to the same date format.

precip_data$DATE <- strptime(precip_data$DATE, "%Y-%m-%d")
PET_data$DATE <- strptime(PET_data$DATE, "%Y-%m-%d")

now we’ll find the common period

first_date <- max(observed_data$DATE[1], precip_data$DATE[1], PET_data$DATE[1])
last_date <- min(observed_data$DATE[length(observed_data$DATE)], precip_data$DATE[length(precip_data$DATE)], PET_data$DATE[length(PET_data$DATE)])

and make a data frame of that length

# make an empty data frame
thames_data <- as.data.frame(matrix(NA,nrow=as.numeric((last_date-first_date)+1), ncol=4))
colnames(thames_data) <-c ("date","PET","precip","obs")
# make the date timeseries
thames_data$date <- seq(first_date, last_date, by="days")
# populate the data frame with the data
thames_data$obs <- observed_data$Qobs[which(observed_data$DATE==thames_data$date[1]):which(observed_data$DATE==thames_data$date[length(thames_data$date)])]
thames_data$precip <- precip_data$Mean_rainfall[which(precip_data$DATE==thames_data$date[1]):which(precip_data$DATE==thames_data$date[length(thames_data$date)])]
thames_data$PET <- PET_data$PET[which(PET_data$DATE==thames_data$date[1]):which(PET_data$DATE==thames_data$date[length(thames_data$date)])]

Interactive time series plot

plot the observed streamflow with the precipitation data

#  convert the observed discharge to runoff (so its in the same units as the precip)
# divide by catchment area (m2) and mulitply by 86.4
thames_data$obs <- (thames_data$obs/9948.0)*86.4
thames_data_xts <- as.xts(thames_data[,3:4], order.by=thames_data$date)
# initiate the dygraph
dygraph(thames_data_xts)%>%
# define the first axis  
dyAxis("obs", name = "y", label = "runoff (mm/day)",
       valueRange = range(thames_data_xts[, "obs"],
                          na.rm = TRUE)* c(0.01, 1.59))%>%
# define the second axis
dyAxis("precip", name = "y2", label = "precip (mm/day)",
                   valueRange = rev(range(thames_data_xts[, "precip"], 
                   na.rm = TRUE)* c(0.01, 2.99)))%>%
# plot the data
dySeries("obs",axis = 'y')%>%
dySeries("precip", axis = 'y2', stepPlot = TRUE,
         fillGraph = TRUE)%>%
dyOptions(colors = RColorBrewer::brewer.pal(3, "Set1")[2:3])%>%
dyRangeSelector()

Hydrological modeling

OK, enough messing with data, lets do some modelling

see this website for a good guide through the model: https://odelaigue.github.io/airGR/tutorial_1_getting_started.html

load the GR package

require(airGR, quietly=TRUE)

prepare the input data in the correct format

BasinObs <- thames_data
colnames(BasinObs) <- c('DatesR','E','P', 'Qobs')

create the InputsModel object - this defines which model we want to run, and defines the variables for the models input data

InputsModel <- CreateInputsModel(FUN_MOD = RunModel_GR4J,DatesR = BasinObs$DatesR,
                                   Precip = BasinObs$P,PotEvap = BasinObs$E)
str(InputsModel)
List of 3
 $ DatesR : POSIXlt[1:19723], format: "1961-01-01" "1961-01-02" "1961-01-03" ...
 $ Precip : num [1:19723] 8.6376 4.571 2.1015 0.0201 7.1859 ...
 $ PotEvap: num [1:19723] 0.367 0.353 0.412 0.32 0.612 ...
 - attr(*, "class")= chr [1:3] "InputsModel" "daily" "GR"
# note NA values of precip and PET are NOT ALLOWED

create the RunOptions object - this defines options for the RunModel_GR4J function

# first define the period to run the model over
Ind_Run <- seq(which(BasinObs$DatesR=="1981-01-01"),
             which(BasinObs$DatesR=="2014-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
str(RunOptions)
List of 6
 $ IndPeriod_WarmUp: int [1:365] 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 ...
 $ IndPeriod_Run   : int [1:12418] 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 ...
 $ IniStates       : num [1:67] 0 0 0 0 0 0 0 0 0 0 ...
 $ IniResLevels    : num [1:2] 0.3 0.5
 $ Outputs_Cal     : chr "Qsim"
 $ Outputs_Sim     : chr [1:16] "DatesR" "PotEvap" "Precip" "Prod" ...
 - attr(*, "class")= chr [1:3] "RunOptions" "GR" "daily"

create the InputsCrit object - define the error metric (choose from RMSE, NSE, KGE or modified KGE (KGE2))

InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_NSE,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
str(InputsCrit)
List of 5
 $ BoolCrit  : logi [1:12418] TRUE TRUE TRUE TRUE TRUE TRUE ...
 $ Qobs      : num [1:12418] 0.635 0.623 0.587 0.571 0.566 ...
 $ transfo   : chr ""
 $ Ind_zeroes: NULL
 $ epsilon   : NULL
 - attr(*, "class")= chr "InputsCrit"

create the CalibOptions object - choose the calibration algorithm

CalibOptions <- CreateCalibOptions(FUN_MOD = RunModel_GR4J,
                                   FUN_CALIB = Calibration_Michel)
str(CalibOptions)
List of 3
 $ FixedParam       : logi [1:4] NA NA NA NA
 $ SearchRanges     : num [1:2, 1:4] 4.59e-05 2.18e+04 -1.09e+04 1.09e+04 4.59e-05 ...
 $ StartParamDistrib: num [1:3, 1:4] 169.017 247.151 432.681 -2.376 -0.649 ...
 - attr(*, "class")= chr [1:3] "CalibOptions" "GR4J" "HBAN"

run the calibration

OutputsCalib <- Calibration_Michel(InputsModel = InputsModel, 
                                   RunOptions = RunOptions,
                                   InputsCrit = InputsCrit,
                                   CalibOptions = CalibOptions,
                                   FUN_MOD = RunModel_GR4J,
                                   FUN_CRIT = ErrorCrit_NSE)
Grid-Screening in progress (0% 20% 40% 60% 80% 100%)
     Screening completed (81 runs)
         Param =  432.681 ,   -0.649 ,   83.096 ,    2.384
         Crit NSE[Q]       = 0.8923
Steepest-descent local search in progress
     Calibration completed (19 iterations, 216 runs)
         Param =  607.894 ,   -0.734 ,   87.357 ,    2.315
         Crit NSE[Q]       = 0.9246

NSE of 0.9246 - not bad at all!

define the parameters found by the calibration routine

Param <- OutputsCalib$ParamFinalR
Param
[1] 607.8936811  -0.7336304  87.3567230   2.3153153

RUN THE MODEL!

OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
str(OutputsModel)
List of 16
 $ DatesR  : POSIXlt[1:12418], format: "1981-01-01 00:00:00" "1981-01-02 00:00:00" "1981-01-03 00:00:00" ...
 $ PotEvap : num [1:12418] 1.347 0.38 0.795 0.57 0.454 ...
 $ Precip  : num [1:12418] 0.0972 0.1211 0.0523 0.1147 0.3248 ...
 $ Prod    : num [1:12418] 352 351 350 350 349 ...
 $ AE      : num [1:12418] 1.127 0.334 0.663 0.488 0.43 ...
 $ Perc    : num [1:12418] 0.387 0.383 0.378 0.374 0.372 ...
 $ PR      : num [1:12418] 0.387 0.383 0.378 0.374 0.372 ...
 $ Q9      : num [1:12418] 0.355 0.35 0.345 0.341 0.338 ...
 $ Q1      : num [1:12418] 0.0398 0.0393 0.0387 0.0382 0.0378 ...
 $ Rout    : num [1:12418] 42.2 42 41.7 41.4 41.2 ...
 $ Exch    : num [1:12418] -0.0592 -0.0577 -0.0564 -0.0551 -0.0539 ...
 $ AExch   : num [1:12418] -0.099 -0.097 -0.0951 -0.0933 -0.0917 ...
 $ QR      : num [1:12418] 0.599 0.578 0.559 0.542 0.526 ...
 $ QD      : num [1:12418] 0 0 0 0 0 ...
 $ Qsim    : num [1:12418] 0.599 0.578 0.559 0.542 0.526 ...
 $ StateEnd: num [1:67] 374.4 45.5 NA NA NA ...
 - attr(*, "class")= chr [1:3] "OutputsModel" "daily" "GR"

use the inbuilt plot function to look at the results

plot(OutputsModel, Qobs=BasinObs$Qobs[Ind_Run])

looking good - but we’ve got some discrepancy at the low flows end. NSE is notorious for this, it is based on the square of the flows, so over-weights the calibration to the high flows. I wonder if the modified KGE can do any better?

# make a few changes to the calibration criteria
InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_KGE2,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
# rerun the calibration
OutputsCalib <- Calibration_Michel(InputsModel = InputsModel, 
                                   RunOptions = RunOptions,
                                   InputsCrit = InputsCrit,
                                   CalibOptions = CalibOptions,
                                   FUN_MOD = RunModel_GR4J,
                                   FUN_CRIT = ErrorCrit_KGE2)
Grid-Screening in progress (0% 20% 40% 60% 80% 100%)
     Screening completed (81 runs)
         Param =  432.681 ,   -0.649 ,   83.096 ,    2.384
         Crit KGE'[Q]      = 0.8589
Steepest-descent local search in progress
     Calibration completed (51 iterations, 502 runs)
         Param =  598.748 ,   -0.690 ,   93.679 ,    2.297
         Crit KGE'[Q]      = 0.9621
# redefine the parameters
Param <- OutputsCalib$ParamFinalR
# rerun the model
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
# plot again
plot(OutputsModel, Qobs=BasinObs$Qobs[Ind_Run])

not much different. Oh well, we can be happy with either of those metric scores. - pause for thought - which parameter set would you choose to use?!

Validation

Let’s do some validation

# go back to the beginning, redefine the period to run on (the period we haven't used for calibration, minus the first year needed for warm up)
Ind_Run <- seq(which(BasinObs$DatesR=="1962-01-01"),
             which(BasinObs$DatesR=="1980-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_KGE2,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
Param <- OutputsCalib$ParamFinalR
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
OutputsCrit <- ErrorCrit_KGE2(InputsCrit = InputsCrit, 
                              OutputsModel = OutputsModel)
Crit. KGE'[Q] = 0.9039
    SubCrit. KGE'[Q] cor(sim, obs, "pearson") = 0.9466 
    SubCrit. KGE'[Q] sd(sim)/sd(obs)          = 0.9253 
    SubCrit. KGE'[Q] mean(sim)/mean(obs)      = 1.0282 

slightly worse than the calibration period (0.9621) but not bad at all

finally, let’s run the model for the whole time period and plot a dygraph so we can look at the timeseries in more detail

Ind_Run <- seq(which(BasinObs$DatesR=="1962-01-01"),
             which(BasinObs$DatesR=="2014-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
Param <- OutputsCalib$ParamFinalR
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
plot_output_data <- as.data.frame(matrix(NA, ncol = 3,
                                         nrow = length(OutputsModel$DatesR)))
colnames(plot_output_data) <- c("Date", "Qsim", "Qobs")
plot_output_data$Date <- OutputsModel$DatesR
plot_output_data$Qsim <- OutputsModel$Qsim
plot_output_data$Qobs <- BasinObs$Qobs[Ind_Run]
plot_output_data_xts <- as.xts(plot_output_data, order.by = plot_output_data$Date)
dygraph(plot_output_data_xts, main="Observed and Simulated Runoff for the Thames at Kingston (Naturalised)")%>%
  dyOptions(colors = RColorBrewer::brewer.pal(3,"Set1")[2:1])%>%
  dyAxis("y", label="Runoff (mm/day)")%>%
  dyRangeSelector()

Thanks for listening! Hope you get to try it out.

Any general questions? Please feel free to post them on facebook page: https://www.facebook.com/groups/1130214777123909/?ref=bookmarks

GR specific questions? Email airGR@irstea.fr


top

EDA

Exploratory Data Analysis including flow duration curve and trend analysis on time-series
Shaun Harrigan


top

Discussion

Please give us feedback at bit.ly/feedbackR

For discussions, please use the Hydrology in R Facebook group.

LS0tCnRpdGxlOiAiUmh5ZHJvIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgdG9jOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmlmIChrbml0cjo6OmlzX2h0bWxfb3V0cHV0KCkpIHsKICBrbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFKQp9CmBgYAoKIyBVc2luZyBSIGluIGh5ZHJvbG9neSAoRUdVMjAxNyBzaG9ydCBjb3Vyc2UpCgoqSW5zdHJ1Y3RvcnMqOiBTaGF1biBIYXJyaWdhbiwgS2F0aWUgU21pdGgsIEJlcnJ5IEJvZXNzZW5rb29sIGFuZCBEYW5pZWwgS2xvdHogIAoqT3JnYW5pemVyKjogQmVycnkgQm9lc3Nlbmtvb2wsIFBoRCBzdHVkZW50IGF0IFBvdHNkYW0gVW5pdmVyc2l0eSAoR2VybWFueSkgIAoqQ29udGFjdCo6IFF1ZXN0aW9ucyBhbmQgZmVlZGJhY2sgYXJlIHdlbGNvbWUgdmlhIDxiZXJyeS1iQGdteC5kZT4KClRoZXNlIHNsaWRlcyBhbmQgYWxsIG90aGVyIGNvdXJzZSBtYXRlcmlhbHMgY2FuIGJlIGZvdW5kIGF0ICAKPGZvbnQgc2l6ZT0iNiI+W2dpdGh1Yi5jb20vYnJyeS9yaHlkcm9dKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRybyk8L2ZvbnQ+ICAgCltEb3dubG9hZCB0aGUgZ2l0aHViIGNvdXJzZSByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vYXJjaGl2ZS9tYXN0ZXIuemlwKQp3aXRoIGFsbCB0aGUgbWF0ZXJpYWxzIGluY2x1ZGluZyB0aGUgZGF0YXNldHMgYW5kIHByZXNlbnRhdGlvbiBzb3VyY2UgY29kZS4gIApUaGlzIGlzIGFuIFtSIE1hcmtkb3duIE5vdGVib29rXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tL3Jfbm90ZWJvb2tzLmh0bWwpLiAgCkZvciBkaXNjdXNzaW9ucywgcGxlYXNlIHZpc2l0IHRoZSAKW0h5ZHJvbG9neSBpbiBSIEZhY2Vib29rIGdyb3VwXShodHRwczovL3d3dy5mYWNlYm9vay5jb20vZ3JvdXBzLzExMzAyMTQ3NzcxMjM5MDkvKS4gIApCZWZvcmUgcnVubmluZyB0aGUgY29kZSBibG9ja3MgYmVsb3csIHdlIHN1Z2dlc3QgdG8gZ2V0IHBhY2thZ2UgaW5zdGFsbGF0aW9uIGluc3RydWN0aW9ucyBieSBydW5uaW5nOgpgYGBSCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Jycnkvcmh5ZHJvL21hc3Rlci9jaGVja3BjLlIiKQpgYGAKClwKCioqQWltIGFuZCBjb250ZW50cyBvZiB0aGlzIHdvcmtzaG9wKioKCldlIHdhbnQgdG86ICAKCiogU2hvdyBvZmYgaG93IGF3ZXNvbWUgUiBpcyBmb3IgaHlkcm9sb2d5IChpdCdzIFItc29tZSFeXikgIAoqIENvbnZpbmNlIHlvdSB0byBzdGFydCBvciBjb250aW51ZSB1c2luZyBSICAKKiBQcm92aWRlIGFsbCB0aGUgY29kZSBmb3IgeW91IGFzIGEgc3RhcnRpbmcgcG9pbnQKCldlIGNhbiBub3Q6ICAKCiogVGVhY2ggeW91IGFjdHVhbCBSIGNvZGluZyAoOTAgbWlucyBpcyB0b28gc2hvcnQgZm9yIGEgdHV0b3JpYWwpCgpXZSBoYXZlIHByZXBhcmVkOgoKKiBbR29vZCBjb2RpbmcgcHJhY3RpY2UsIHJlcG9ydCBnZW5lcmF0aW9uXSgjcmVwb3J0KSAoUnN0dWRpbywgYHJtYXJrZG93bmAsIFIgbm90ZWJvb2spCiogW1VzaW5nIFIgYXMgR0lTXSgjZ2lzKSAocmVhZGluZyBhIHJhaW5mYWxsIHNoYXBlZmlsZSArIEtyaWdpbmcsIGBzZmAgKyBgbGVhZmxldGAgKyBgbWFwdmlld2AgKyBgT1NNc2NhbGVgKQoqIFtSaXZlciBkaXNjaGFyZ2UgdGltZS1zZXJpZXNdKCNkaXNjaGFyZ2UpIHZpc3VhbGlzYXRpb24gYW5kIGV4dHJlbWUgdmFsdWUgc3RhdGlzdGljcyAoYGFuaW1hdGlvbmAgKyBgZXh0cmVtZVN0YXRgKQoqIFtIeWRyb2xvZ2ljYWwgbW9kZWxsaW5nXSgjaHlkbW9kKSB3aXRoIGBhaXJHUmAKKiBbRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpc10oI2VkYSkgaW5jbHVkaW5nIGZsb3cgZHVyYXRpb24gY3VydmUgYW5kIHRyZW5kIGFuYWx5c2lzIG9uIHRpbWUtc2VyaWVzCgpcIAoKQmVmb3JlIHdlIGdldCBzdGFydGVkLCBwbGVhc2UgbGV0IHVzIGtub3cgeW91ciBjdXJyZW50IFIga25vd2xlZGdlIGxldmVsIGJ5IGZpbGxpbmcgb3V0IHRoZSBzaG9ydCBzdXJ2ZXkgYXQgIAo8Zm9udCBzaXplPSI2Ij5bYml0Lmx5L2tub3dSXShodHRwczovL2JpdC5seS9rbm93Uik8L2ZvbnQ+IAoKXAoKW3RvcF0oI3RvcCkKCiMgUmVwb3J0Ckdvb2QgY29kaW5nIHByYWN0aWNlLCByZXBvcnQgZ2VuZXJhdGlvbiAoUnN0dWRpbywgYHJtYXJrZG93bmAsIFIgbm90ZWJvb2spICAKKipEYW5pZWwgS2xvdHoqKgoKIyMjIEludHJvZHVjdGlvbgoKIVtnb2Fsc10oZGFuaWVsL2ludHJvL2dvYWxzLmpwZWcpCgojIyMgV2h5IEkgdXNlIFIKCldoeSBJIGRpZCBub3QgdXNlOiAKCiFbZXF1YWxzXShkYW5pZWwvaW50cm8vZXF1YWxzLmpwZWcpCgoKV2hhdHMgZ3JlYXQgYWJvdXQgUjogCmBgYHtyfQogIGxpYnJhcnkoZ2dwbG90MikKICB0ZXN0X2RhdGEgPC0gbXBnCiAgdGVzdF9wbG90IDwtIGdncGxvdCh0ZXN0X2RhdGEsIGFlcyhkaXNwbCwgaHd5LCBjb2xvdXIgPSBjbGFzcykpICsgCiAgICBnZW9tX3BvaW50KCkKICB0ZXN0X3Bsb3QKYGBgCgpXaHkgSSBkZWNpZGVkIHRvIHVzZSBSOiAKCiFbcGlwZV0oZGFuaWVsL2ludHJvL21hZ3JpdHRyLmpwZWcpCgpQcmV2aW91c2x5OgpgYGB7cn0KICBhZ2dyZWdhdGlvbl9mdW5jdGlvbiA8LSBmdW5jdGlvbih4KSB7CiAgICByb3VuZChtZWFuKHgpLDIpCiAgfQogIG10Y2Fyc19zdWJzZXQgPC0gc3Vic2V0KG10Y2FycyxocCA+IDEwMCkKICBtdGNhcnNfYWdncmVnYXRlZCA8LSBhZ2dyZWdhdGUoLiB+IGN5bCwgZGF0YSA9IG10Y2Fyc19zdWJzZXQsIEZVTiA9IGFnZ3JlZ2F0aW9uX2Z1bmN0aW9uKQogIGNhcl9kYXRhMSA8LSB0cmFuc2Zvcm0obXRjYXJzX2FnZ3JlZ2F0ZWQsIGtwbCA9IG1wZyowLjQyNTEpCiAgcHJpbnQoY2FyX2RhdGExKQpgYGAKCk5vdzoKYGBge3J9CmxpYnJhcnkobWFncml0dHIpCmNhcl9kYXRhMiA8LSAKICBtdGNhcnMgJT4lCiAgc3Vic2V0KGhwID4gMTAwKSAlPiUKICBhZ2dyZWdhdGUoLiB+IGN5bCwgZGF0YSA9IC4sIEZVTiA9IC4gJT4lIG1lYW4gJT4lIHJvdW5kKDIpKSAlPiUKICB0cmFuc2Zvcm0oa3BsID0gbXBnICU+JSBtdWx0aXBseV9ieSgwLjQyNTEpKSAlPiUKICBwcmludApgYGAKCgoqKmJ0dzoqKgpZb3UgY2FuIGludGVncmF0ZSBvdGhlciBwcm9ncmFtbWluZyBsYW5ndWFnZXMgd2l0aCBlYXNlLiBIZXJlIGFuIGV4YW1wbGUgZnJvbSAKW1lpaHVpIFhpZV0oaHR0cHM6Ly95aWh1aS5uYW1lL2tuaXRyL2RlbW8vZW5naW5lcy8pIGZvciB0aGUgdXNlIG9mIGBGb3J0cmFuYCAKaW4gUm1hcmtkb3duOgoKMS4gQ29tcGlsZSBDb2RlOgogICAgYGBge3IgY29tcGZvcnQsIGVuZ2luZT0nZm9ydHJhbicsIHJlc3VsdHM9J2hpZGUnLCBldmFsPUZBTFNFfQogICAgQyBGb3J0cmFuIHRlc3QKICAgICAgICAgIHN1YnJvdXRpbmUgZmV4cChuLCB4KQogICAgICAgICAgZG91YmxlIHByZWNpc2lvbiB4CiAgICBDICBvdXRwdXQKICAgICAgICAgIGludGVnZXIgbiwgaQogICAgQyAgaW5wdXQgdmFsdWUKICAgICAgICAgIGRvIDEwIGk9MSxuCiAgICAgICAgICAgICB4PWRleHAoZGNvcyhkc2luKGRibGUoZmxvYXQoaSkpKSkpCiAgICAgIDEwICBjb250aW51ZQogICAgICAgICAgcmV0dXJuCiAgICAgICAgICBlbmQKICAgIGBgYAoKMi4gUnVuIENvZGU6CiAgICBgYGB7ciB0ZXN0Zm9ydCwgY29sbGFwc2U9VFJVRSwgZXZhbCA9IEZBTFNFfQogICAgcmVzID0gLkZvcnRyYW4oImZleHAiLCBuPTEwMDAwMEwsIHg9MCkKICAgIHN0cihyZXMpCiAgICBgYGAKCkJlIGhhcHB5IHdpdGggdGhlIHJlc3VsdDogCj4gICAgIGAjIyBMaXN0IG9mIDJgCj4gICAgIGAjIyAgJCBuOiBpbnQgMTAwMDAwYAo+ICAgICBgIyMgICQgeDogbnVtIDIuNzJgCgotLS0KCiMjIE1hcmtkb3duIApIVCoqTSoqTDogSHlwZXJUZXh0ICoqTWFya2Rvd24qKiBMYW5ndWFnZQoKSm9obiBHcnViZXI6CgpbIVtKb2huIEdydWJlcl0oZGFuaWVsL2ludHJvL0pvaG5fR3J1YmVyX3dpa2kuanBlZyldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0pvaG5fR3J1YmVyKQoKCkNvbXBhcmlzb246IE1hcmtkb3duIHZzLiBMYXRleCAKWyFbQ29tcGFyaXNvbl0oZGFuaWVsL2ludHJvL3lpaHVpX2xhdGV4LXZzLW1hcmtkb3duLnBuZyldKGh0dHBzOi8veW91dHUuYmUvMnl2VzBPXzd4T2cpCgoKUnN0dWRpbyBwcm92aWRlcyBjaGVhdC1zaGVldHMgd2l0aCB0aGUgbW9zdCBpbXBvcnRhbnQgaW5mb3JtYXRpb25zIGFib3V0IG1hbnkgCm9mIHRoZWlyICJmYXZvcml0ZSIgcGFja2FnZXMgJiBzb2Z0d2FyZToKClshW0NoZWF0IFNoZWV0XShkYW5pZWwvaW50cm8vUm1hcmtkb3duX2NoZWF0c2hlZXQucG5nKV0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLykKCiMgUm1hcmtkb3duCkluIFJzdHVkaW86IAoKPGRpdiBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHdpZHRoPSI2MDBweCIgc3JjPSJkYW5pZWwvcmVwb3J0cy9SbWFyazEucG5nIiBhbHQ9IlJtYXJrMSIgLz4KPC9kaXY+Cgo8ZGl2IGFsaWduPSJjZW50ZXIiPgogIDxpbWcgd2lkdGg9IjYwMHB4IiBzcmM9ImRhbmllbC9yZXBvcnRzL1JtYXJrMi5wbmciIGFsdD0iUm1hcmsyIiAvPgo8L2Rpdj4KCjxkaXYgYWxpZ249ImNlbnRlciI+CiAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL3JlcG9ydHMvUm1hcmszLnBuZyIgYWx0PSJSbWFyazIiIC8+CjwvZGl2PgoKIk5hdGl2ZSIgRm9ybWF0czoKCi0gW2h0bWxdKGRhbmllbC9zaG93L1JtYXJrLmh0bWwpIAotIFtwZGZdKGRhbmllbC9zaG93L1JtYXJrLnBkZikgCi0gW3dvcmRdKGRhbmllbC9zaG93L1JtYXJrLmRvY3MpCgpNdWNoIG1vcmUgcG9zc2libGUgaWYgeW91IGFkcmVzcyBwYW5kb2MgZGlyZWN0bHk6IApbIVtwYW5kb2NdKGRhbmllbC9yZXBvcnRzL3BhbmRvY19kaWFncmFtLmpwZyldKGh0dHA6Ly9wYW5kb2Mub3JnLykKCkluZm9ybWF0aW9uIGluIHRoZSB0ZXh0IGNhbiBiZSBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgd2l0aCB0aGUgcmVzdCBvZiB0aGUgCmRvY3VtZW50OgpbIVt0aW1lIGZvciBjb2ZmZWVdKGRhbmllbC9yZXBvcnRzL2NvZmZlZS5wbmcpCgojIyMgRXhhbXBsZXMKCiMjIyMgU21hbGwgV2Vic2l0ZXMKPGRpdiBhbGlnbj0iY2VudGVyIj4KICA8YSBocmVmID0gImh0dHA6Ly9yc3R1ZGlvLmdpdGh1Yi5pby90dWZ0ZS8iPgogICAgPGltZyB3aWR0aD0iNjAwcHgiIHNyYz0iZGFuaWVsL2V4YW1wbGVzL3N3X3R1ZnRlLnBuZyIgYWx0PSJDYXltYW4gVGhlbWUiIC8+CiAgPC9hPgo8L2Rpdj4KCjxkaXYgYWxpZ249ImNlbnRlciI+CiAgPGEgaHJlZiA9ICJodHRwOi8veWl4dWFuLmNvcy5uYW1lL3ByZXR0eWRvYy9jYXltYW4uaHRtbCI+CiAgICA8aW1nIHdpZHRoPSI2MDBweCIgc3JjPSJkYW5pZWwvZXhhbXBsZXMvc3dfY2F5bWFuLnBuZyIgYWx0PSJDYXltYW4gVGhlbWUiIC8+CiAgPC9hPgo8L2Rpdj4KCiMjIyMgQm9va3MgKGJsb2dkb3duKQpbIVtib29rZG93bjFdKGRhbmllbC9leGFtcGxlcy9ib29rZG93bl8xLnBuZyldKGh0dHBzOi8vYm9va2Rvd24ub3JnLykKWyFbYm9va2Rvd24yXShkYW5pZWwvZXhhbXBsZXMvYm9va2Rvd25fMi5wbmcpXShodHRwczovL2Jvb2tkb3duLm9yZy8pCgojIyMgQmxvZ3MgKGh1Z28pClshW2h1Z29dKGRhbmllbC9leGFtcGxlcy9ibG9nc19odWdvMS5wbmcpXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ibG9nZG93bi8pCgojIyMjIFByZXNlbnRhdGlvbnMKWyFbYm9va2Rvd24xXShkYW5pZWwvZXhhbXBsZXMvcHJlc2VudGF0aW9uc18xLnBuZyldKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcmV2ZWFsanNfcHJlc2VudGF0aW9uX2Zvcm1hdC5odG1sKQoKIyMjIyBXaWRnZXRzIApbIVtjcmFuLWdhdWdlXShkYW5pZWwvZXhhbXBsZXMvd2lkZ2V0c18xLnBuZyldKGh0dHBzOi8vZ2FsbGVyeS5zaGlueWFwcHMuaW8vY3Jhbi1nYXVnZS8pCgojIyMjIEFwcHMgKFNoaW55KSAKWyFbc2hpbnldKGRhbmllbC9leGFtcGxlcy9zaGlueV8xLmpwZWcpXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tL2F1dGhvcmluZ19zaGlueS5odG1sKQpbIVtzaGlueTJdKGRhbmllbC9leGFtcGxlcy9zaGlueV8yX2dhbGxlcnkucG5nKV0oaHR0cHM6Ly9zaGlueS5yc3R1ZGlvLmNvbS9nYWxsZXJ5L3N1cGVyemlwLWV4YW1wbGUuaHRtbCkKIVtzaGlueTJdKGRhbmllbC9leGFtcGxlcy9zaGlueV8zX2pvaGFubmVzLnBuZykKCgpcClt0b3BdKCN0b3ApCgojIEdJUwpVc2luZyBSIGFzIEdJUyAocmVhZGluZyBhIHJhaW5mYWxsIHNoYXBlZmlsZSArIEtyaWdpbmcsIGBzZmAgKyBgbGVhZmxldGAgKyBgbWFwdmlld2AgKyBgT1NNc2NhbGVgKSAgCioqQmVycnkgQm9lc3Nlbmtvb2wqKgoKIyMjIFNoYXBlZmlsZXMKClJlYWRpbmcgc2hhcGVmaWxlcyB3aXRoIGBtYXB0b29sczo6cmVhZFNoYXBlU3BhdGlhbGAgYW5kIGByZ2RhbDo6cmVhZE9HUmAgaXMgb2Jzb2xldGUuICAKSW5zdGVhZCwgdXNlIGBzZjo6c3RfcmVhZGAuIGBzZmAgaXMgb24gQ1JBTiBzaW5jZSBvY3QgMjAxNi4gIApNYWluIHJlYWN0aW9uIHdoZW4gdXNpbmcgc2Y6ICJXb3csIHRoYXQgaXMgZmFzdCEiICAKW0Rvd25sb2FkIHRoZSBzaGFwZWZpbGVdKGh0dHBzOi8vbWluaGFza2FtYWwuZ2l0aHViLmlvL0Rvd25HaXQvIy9ob21lP3VybD1odHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vdHJlZS9tYXN0ZXIvcHJlc2VudGF0aW9ucy9kYXRhL1ByZWNCcmFuZGVuYnVyZykgCm9yIGJldHRlcjogW2Rvd25sb2FkIHRoZSB3aG9sZSBnaXRodWIgY291cnNlIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby9hcmNoaXZlL21hc3Rlci56aXApCgpgYGB7cn0KcmFpbiA8LSBzZjo6c3RfcmVhZCgiZGF0YS9QcmVjQnJhbmRlbmJ1cmcvbmllZGVyc2NobGFnLnNocCIpCmBgYAoKQ2VudHJhbCBwb2ludHMgb2YgcmFpbmZhbGwgVGhpZXNzZW4gcG9seWdvbnMKYGBge3J9CmNlbnRyb2lkcyA8LSBzZjo6c3RfY2VudHJvaWQocmFpbikKY2VudHJvaWRzIDwtIHNmOjpzdF9jb29yZGluYXRlcyhjZW50cm9pZHMpCmNlbnRyb2lkcyA8LSBhcy5kYXRhLmZyYW1lKGNlbnRyb2lkcykKYGBgCgpbdG9wXSgjdG9wKQoKIyMjIFBsb3R0aW5nLCBtYXBzCgpTdGF0aWMgcGxvdDoKYGBge3J9CnBsb3QocmFpblssMV0pCmBgYAoKU3RhdGljIG1hcDoKYGBge3J9CnByaiA8LSBzZjo6c3RfY3JzKHJhaW4pJHByb2o0c3RyaW5nCmNlbnRfbGwgPC0gT1NNc2NhbGU6OnByb2plY3RQb2ludHMoWSxYLCBkYXRhPWNlbnRyb2lkcywgdG89T1NNc2NhbGU6OnBsbCgpLCBmcm9tPXByaikKI21hcF9zdGF0aWMgPC0gT1NNc2NhbGU6OnBvaW50c01hcCh5LHgsIGNlbnRfbGwsIGZ4PTAuMDgsIHR5cGU9Im1hcHRvb2xraXQtdG9wbyIsIHpvb209NikKI3NhdmUobWFwX3N0YXRpYywgZmlsZT0iZGF0YS9tYXBfc3RhdGljLlJkYXRhIikKbG9hZCgiZGF0YS9tYXBfc3RhdGljLlJkYXRhIikKT1NNc2NhbGU6OnBvaW50c01hcCh5LHgsIGNlbnRfbGwsIG1hcD1tYXBfc3RhdGljKQpgYGAKCkludGVyYWN0aXZlIG1hcDoKYGBge3J9CmxpYnJhcnkobGVhZmxldCkKY2VudF9sbCRpbmZvIDwtIHBhc3RlMChzYW1wbGUobGV0dGVycyxucm93KGNlbnRfbGwpLFRSVUUpLCAiLCAiLCByb3VuZChjZW50X2xsJHgsMiksIAogICAgICAgICAgICAgICAgICAgICAgICIsICIsIHJvdW5kKGNlbnRfbGwkeSwyKSkKbGVhZmxldChjZW50X2xsKSAlPiUgYWRkVGlsZXMoKSAlPiUgYWRkQ2lyY2xlTWFya2Vycyhsbmc9fngsIGxhdD1+eSwgcG9wdXA9fmluZm8pCmBgYAoKSW50ZXJhY3RpdmUgbWFwIG9mIHNoYXBlZmlsZToKYGBge3J9CiMgbWFrZSBzdXJlIHRvIGhhdmUgaW5zdGFsbGVkIHRoZSBkZXZlbG9wbWVudCB2ZXJzaW9uIG9mIG1hcHZpZXc6IAojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZW52aXJvbm1lbnRhbGluZm9ybWF0aWNzLW1hcmJ1cmcvbWFwdmlldyIsIHJlZiA9ICJkZXZlbG9wIikKbGlicmFyeShiZXJyeUZ1bmN0aW9ucykgIyBjbGFzc2lmeSwgc2VxUGFsCmNvbCA8LSBzZXFQYWwobj0xMDAsIGNvbG9ycz1jKCJyZWQiLCJ5ZWxsb3ciLCJibHVlIikpW2NsYXNzaWZ5KHJhaW4kUDEpJGluZGV4XQptYXB2aWV3OjptYXB2aWV3KHJhaW4sIGNvbC5yZWdpb25zPWNvbCkKYGBgCgpbdG9wXSgjdG9wKQoKIyMjIEtyaWdpbmcKClBsb3Qgb3JpZ2luYWwgcG9pbnRzIGNvbG9yZWQgYnkgdGhpcmQgZGltZW5zaW9uOgpgYGB7cn0KcGNvbCA8LSBjb2xvclJhbXBQYWxldHRlKGMoInJlZCIsInllbGxvdyIsImJsdWUiKSkoNTApCnggPC0gY2VudHJvaWRzJFggIyB1c2UgY2VudF9sbCR4IGZvciBwcm9qZWN0ZWQgZGF0YQp5IDwtIGNlbnRyb2lkcyRZCmJlcnJ5RnVuY3Rpb25zOjpjb2xQb2ludHMoeCwgeSwgcmFpbiRQMSwgYWRkPUZBTFNFLCBjb2w9cGNvbCkKYGBgCgpDYWxjdWxhdGUgdGhlIHZhcmlvZ3JhbSBhbmQgZml0IGEgc2VtaXZhcmlhbmNlIGN1cnZlCmBgYHtyfQpsaWJyYXJ5KGdlb1IpCmdlb3ByZWMgPC0gYXMuZ2VvZGF0YShjYmluZCh4LHkscmFpbiRQMSkpCnZhcmlvIDwtIHZhcmlvZyhnZW9wcmVjLCBtYXguZGlzdD0xMzAwMDApICMgb3RoZXIgbWF4ZGlzdCBmb3IgbGF0LWxvbiBkYXRhCmZpdCA8LSB2YXJpb2ZpdCh2YXJpbykKcGxvdCh2YXJpbykKbGluZXMoZml0KQpgYGAKCkRldGVybWluZSBhIHVzZWZ1bCByZXNvbHV0aW9uIAooa2VlcCBpbiBtaW5kIHRoYXQgY29tcHV0aW5nIHRpbWUgcmlzZXMgZXhwb25lbnRpYWxseSB3aXRoIGdyaWQgc2l6ZSkKYGBge3J9CiMgZGlzdGFuY2UgdG8gY2xvc2VzdCBvdGhlciBwb2ludDoKZCA8LSBzYXBwbHkoMTpsZW5ndGgoeCksIGZ1bmN0aW9uKGkpCiAgICAgICAgICAgIG1pbihiZXJyeUZ1bmN0aW9uczo6ZGlzdGFuY2UoeFtpXSwgeVtpXSwgeFstaV0sIHlbLWldKSkgKQojIGZvciBsYXQtbG9uZyBkYXRhIHVzZSAoMjAxNy1BcHIgb25seSBhdmFpbGFibGUgaW4gZ2l0aHViIHZlcnNpb24gb2YgT1NNc2NhbGUpCiMgZCA8LSBPU01zY2FsZTo6bWF4RWFydGhEaXN0KHkseCwgZGF0YT1jZW50X2xsLCBmdW49bWluKQpoaXN0KGQvMTAwMCwgYnJlYWtzPTIwLCBtYWluPSJkaXN0YW5jZSB0byBjbG9zZXN0IGdhdWdlIFtrbV0iKQptZWFuKGQvMTAwMCkgIyA4IGttCmBgYAoKUGVyZm9ybSBrcmlnaW5nIG9uIGEgZ3JpZCB3aXRoIHRoYXQgcmVzb2x1dGlvbiAKYGBge3J9CnJlcyA8LSAxMDAwICMgMSBrbSwgc2luY2Ugc3RhdGlvbnMgYXJlIDgga20gYXBhcnQgb24gYXZlcmFnZQpncmlkIDwtIGV4cGFuZC5ncmlkKHNlcShtaW4oeCksbWF4KHgpLHJlcyksCiAgICAgICAgICAgICAgICAgICAgc2VxKG1pbih5KSxtYXgoeSkscmVzKSkKa3JpY28gPC0ga3JpZ2UuY29udHJvbCh0eXBlLmtyaWdlPSJPSyIsIG9iai5tb2RlbD1maXQpCiNrcm9iaiA8LSBrcmlnZS5jb252KGdlb3ByZWMsIGxvYz1ncmlkLCBrcmlnZT1rcmljbykKI3NhdmUoa3JvYmosIGZpbGU9ImRhdGEva3JvYmouUmRhdGEiKQpsb2FkKCJkYXRhL2tyb2JqLlJkYXRhIikgIyBsaW5lIGFib3ZlIGlzIHRvbyBzbG93IGZvciByZWNyZWF0aW9uIGVhY2ggdGltZQpgYGAKClBsb3QgdGhlIGludGVycG9sYXRlZCB2YWx1ZXMgd2l0aCBgaW1hZ2VgIG9yIGFuIGVxdWl2YWxlbnQgZnVuY3Rpb24gCihzZWUgW1JjbGlja10oaHR0cHM6Ly9naXRodWIuY29tL2JycnkvcmNsaWNrKSA0LjE1KSBhbmQgYWRkIGNvbnRvdXIgbGluZXMuCmBgYHtyfQpwYXIobWFyPWMoMCwzLDAsMykpCmdlb1I6OjppbWFnZS5rcmlnaW5nKGtyb2JqLCBjb2w9cGNvbCkKY29sUG9pbnRzKHgsIHksIHJhaW4kUDEsIGNvbD1wY29sLCBsZWdhcmdzPWxpc3QoaG9yaXo9RiwgdGl0bGU9IlByZWMiLHkxPTAuMSx4MT0wLjkpKQpwb2ludHMoeCx5KQpwbG90KHJhaW4sIGNvbD1OQSwgYWRkPVRSVUUpCmBgYApcClt0b3BdKCN0b3ApCgojIERpc2NoYXJnZQpSaXZlciBkaXNjaGFyZ2UgdGltZS1zZXJpZXMgdmlzdWFsaXNhdGlvbiBhbmQgZXh0cmVtZSB2YWx1ZSBzdGF0aXN0aWNzIChgYW5pbWF0aW9uYCArIGBleHRyZW1lU3RhdGApICAKKipCZXJyeSBCb2Vzc2Vua29vbCoqCgojIyMgUmVhZCwgcGxvdCBhbmQgYWdncmVnYXRlIGRhdGEKCkRhdGFzZXRzIGZyb20gPGh0dHA6Ly9ucmZhLmNlaC5hYy51ay9kYXRhL3N0YXRpb24vbWVhbmZsb3cvMzkwNzI+ICAKRG93bmxvYWQgCltkaXNjaGFyZ2UxXShodHRwczovL2dpdGh1Yi5jb20vYnJyeS9yaHlkcm8vdHJlZS9tYXN0ZXIvcHJlc2VudGF0aW9ucy9kYXRhL2Rpc2NoYXJnZTM5MDcyLmNzdiksCltkaXNjaGFyZ2UyX3h4eF0oaHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvL3RyZWUvbWFzdGVyL3ByZXNlbnRhdGlvbnMvZGF0YS9kaXNjaGFyZ2UuY3N2KSwgCltkaXNjaGFyZ2UzX3h4eF0oaHR0cHM6Ly9naXRodWIuY29tL2Jycnkvcmh5ZHJvL3RyZWUvbWFzdGVyL3ByZXNlbnRhdGlvbnMvZGF0YS9kaXNjaGFyZ2UuY3N2KSwgCm9yIGJldHRlcjogW2Rvd25sb2FkIHRoZSB3aG9sZSBnaXRodWIgY291cnNlIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9icnJ5L3JoeWRyby9hcmNoaXZlL21hc3Rlci56aXApCgpSZWFkIGFuZCB0cmFuc2Zvcm0gZGF0YQpgYGB7cn0KUSA8LSByZWFkLnRhYmxlKCJkYXRhL2Rpc2NoYXJnZTM5MDcyLmNzdiIsIHNraXA9MTksIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBmaWxsPVRSVUUpWywxOjJdCmNvbG5hbWVzKFEpIDwtIGMoImRhdGUiLCJSb3lhbF9XaW5kc29yX1BhcmsiKQpRJGRhdGUgPC0gYXMuRGF0ZShRJGRhdGUsIGZvcm1hdD0iJVktJW0tJWQiKQpgYGAKCkV4YW1pbmUgZGF0YQpgYGB7cn0KaGVhZChRKQpgYGAKCmBgYHtyfQpzdHIoUSkKYGBgCgpTaW1wbGUgdGltZSBzZXJpZXMgcGxvdApgYGB7cn0KcGxvdChRLCB0eXBlPSJsIiwgY29sPSJibHVlIikKYGBgCgpQdWJsaWNhdGlvbi1yZWFkeSBncmFwaGljcwpgYGB7cn0KcG5nKCJEaXNjaGFyZ2VWaXMucG5nIiwgd2lkdGg9MjAsIGhlaWdodD0xMCwgdW5pdHM9ImNtIiwgcmVzPTUwMCkKI3BkZigiRGlzY2hhcmdlVmlzLnBkZiIsIHdpZHRoPTIwLzIuNSwgaGVpZ2h0PTEwLzIuNSkgIyB2ZWN0b3IgZ3JhcGgKcGFyKG1hcj1jKDMuNSwzLjUsMi41LDAuMiksIG1ncD1jKDIuMywwLjcsMCksIGxhcz0xKQpwbG90KFEsIHR5cGU9ImwiLCBjb2w9ImJsdWUiLCBtYWluPSJOUkZBOiBUaGFtZXNcblJveWFsIFdpbmRzb3IgUGFyayIsCiAgICAgeGxhYj0iRGF0ZSIsIHlsYWI9IkRpc2NoYXJnZSAgW21cVXswMEIzfS9zXSIpCmRldi5vZmYoKQpgYGAKCkFubnVhbCBtYXhpbWEsIEdlcm1hbiBoeWRyb2xvZ2ljYWwgeWVhciBzcGxpdCBhdCBPY3QgMzEKYGBge3J9ClEkaHlkeWVhciA8LSBhcy5udW1lcmljKGZvcm1hdChRJGRhdGUrNjEsICIlWSIpKQphbm5tYXggPC0gdGFwcGx5KFEkUm95YWxfV2luZHNvcl9QYXJrLCBRJGh5ZHllYXIsIG1heCwgbmEucm09VFJVRSkKYW5ubWF4IDwtIGFubm1heFstMV0KaHlkeWVhciA8LSBhcy5udW1lcmljKG5hbWVzKGFubm1heCkpIApwbG90KGh5ZHllYXIsIGFubm1heCwgdHlwZT0ibyIsIGxhcz0xKQpgYGAKCiMjIyBFeHRyZW1lIHZhbHVlIHN0YXRpc3RpY3MKCmBgYHtyfQpsaWJyYXJ5KGV4dHJlbWVTdGF0KQpkbGYgPC0gZGlzdExmaXQoYW5ubWF4KQpwbG90TGZpdChkbGYpCnBsb3RMZml0KGRsZiwgY2RmPVRSVUUpCmRsZSA8LSBkaXN0TGV4dHJlbWUoZGxmPWRsZiwgUlBzPWMoNSwxMCw1MCwxMDApLCBncGQ9RkFMU0UpCnBsb3RMZXh0cmVtZShkbGUpCmBgYAoKTG9nYXJpdGhtaWMgcGxvdCB3aXRoIG1hbnkgZml0dGVkIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbnMKYGBge3J9CnBsb3RMZXh0cmVtZShkbGUsIG5iZXN0PTE2LCBsb2c9VFJVRSkKYGBgCgpSZXR1cm4gdmFsdWVzIChkaXNjaGFyZ2UgZXN0aW1hdGVzIGZvciBnaXZlbiByZXR1cm4gcGVyaW9kcykKYGBge3J9CmRsZiRyZXR1cm5sZXYKYGBgCkluIHJlYWxpdHksIHBsZWFzZSB1c2Ugbm9uLXN0YXRpb25hcnkgRVZTIQoKVW5jZXJ0YWludHkgYmFuZCBmb3IgV2FrZWJ5IGRpc3RyaWJ1dGlvbiBmaXQgZXN0aW1hdGUKYGBge3J9CmRsZV9ib290IDwtIGRpc3RMZXhCb290KGRsZSwgbj0xMCwgbmJlc3Q9MSkKcGxvdExleEJvb3QoZGxlX2Jvb3QsIGRpc3Rjb2w9ImdyZWVuIikKYGBgCgoKTW9yZSBkZXRhaWxzIGluIHRoZSBwYWNrYWdlIHZpZ25ldHRlCmBgYHtyfQp2aWduZXR0ZSgiZXh0cmVtZVN0YXQiKQpgYGAKCgojIyMgQW5pbWF0ZWQgbW92aWUKCgpSZWFkIGRhdGEgZnJvbSBzZXZlcmFsIGRpc2NoYXJnZSBzdGF0aW9ucwpgYGB7cn0KaWYoRkFMU0UpewpRMiA8LSByZWFkLnRhYmxlKCJkYXRhL2Rpc2NoYXJnZV9fX19fLmNzdiIsIHNraXA9MTksIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBmaWxsPVRSVUUpWywxOjJdCmNvbG5hbWVzKFEyKSA8LSBjKCJkYXRlIiwiZHVtbXkxIik7ICBRMiRkYXRlIDwtIGFzLkRhdGUoUTIkZGF0ZSwgZm9ybWF0PSIlWS0lbS0lZCIpClEzIDwtIHJlYWQudGFibGUoImRhdGEvZGlzY2hhcmdlX19fX18uY3N2Iiwgc2tpcD0xOSwgaGVhZGVyPVRSVUUsIHNlcD0iLCIsIGZpbGw9VFJVRSlbLDE6Ml0KY29sbmFtZXMoUTMpIDwtIGMoImRhdGUiLCJkdW1teTIiKTsgIFEzJGRhdGUgPC0gYXMuRGF0ZShRMyRkYXRlLCBmb3JtYXQ9IiVZLSVtLSVkIikKfQpRMiA8LSBRIDsgUTJbLDJdIDwtIFEyWywyXSArIDEwMCA7IFEyIDwtIFEyWy0oMToxMDApLF0KUTMgPC0gUSA7IFEzWywyXSA8LSBRM1ssMl0gLSAgNTAgOyBRMyA8LSBoZWFkKFEzLCAtMzY1KjMpCmNvbG5hbWVzKFEyKVsyXSA8LSAiZHVtbXkxIjsgY29sbmFtZXMoUTMpWzJdIDwtICJkdW1teTIiCmBgYAoKTWVyZ2UgZGF0YXNldHMKYGBge3J9CmRpcyA8LSBSZWR1Y2UoZnVuY3Rpb24oLi4uKSBtZXJnZSguLi4sIGFsbD1UKSwgbGlzdChRLFEyLFEzKSkKYGBgCgpDb2RlIHRvIGdlbmVyYXRlIG9uZSBtb3ZpZSBzbGljZQpgYGB7cn0KbGlicmFyeShiZXJyeUZ1bmN0aW9ucykgIyBmb3IgbGltMCwgbW9udGhBeGlzLCB0ZXh0RmllbGQsIGV0YwoKc2NlbmUgPC0gZnVuY3Rpb24oaSwgdmxjbm90ZT1UUlVFLCBjZXg9MS4yKQp7CiBzZWwgPC0gMDoxMjAKIGRpczIgPC0gZGlzW2kgKyBzZWwsIF0KIHN0YXQgPC0gYygiUm95YWxfV2luZHNvcl9QYXJrIiwgImR1bW15MSIsICJkdW1teTIiKQogY29sIDwtIGMoInJlZCIsICJibHVlIiwgIm9yYW5nZSIpCiBuYW1lcyhjb2wpIDwtIHN0YXQKIHBsb3QoZGlzMiRkYXRlLGRpczIkUm95YWxfV2luZHNvcl9QYXJrLCB0eXBlPSJuIiwgeWxpbT1saW0wKDUwMCksICBsYXM9MSwgCiAgICAgIHhheHQ9Im4iLCB5YXh0PSJuIiwgY2V4LmxhYj1jZXgsIHhsYWI9IiIsIAogICAgICB5bGFiPSJEaXNjaGFyZ2UgIFttXFV7MDBCM30vc10iLCB4YXhzPSJpIikKIGF4aXMoMiwgY2V4LmF4aXM9Y2V4LCBsYXM9MSkKIFN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAiQyIpCiBtb250aEF4aXMobWlkbW9udGg9VFJVRSwgZm9ybWF0PSIlYlxuJVkiLCBjZXg9Y2V4LCBtZ3A9YygzLDMuNSwwKSkKIGFibGluZShoPTE6NioxMDAsIHY9ZGlzMiRkYXRlW2Zvcm1hdChkaXMyJGRhdGUsIiVkIik9PSIwMSJdLCBjb2w9OCkKIGZvcihzIGluIHN0YXQpIGxpbmVzKGRpczIkZGF0ZSwgZGlzMlssc10sIGx3ZD00LCBjb2w9Y29sW3NdKQogeGkgPC0gc2VxUihzZWwsbGVuPWxlbmd0aChzdGF0KSsyKVstMV0KIHhpIDwtIGhlYWQoeGksIC0xKQogdGV4dEZpZWxkKGRpczIkZGF0ZVt4aV0sIGRpYWcoYXMubWF0cml4KGRpczJbeGksc3RhdF0pKSwgc3RhdCwgY2V4PWNleCwgY29sPWNvbCkKIGJveCgpCiBpZih2bGNub3RlKSBtdGV4dCgiVkxDOiAnZSc6IHNpbmdsZSBmcmFtZSBmb3J3YXJkXG4nU0hJRlQrTEVGVCc6IGZldyBzZWNvbmRzIGJhY2siLAogICAgICAgICAgICAgICAgICAgc2lkZT0zLCBsaW5lPS05LCBvdXRlcj1UUlVFLCBhZGo9MC45NSwgY2V4PWNleCkKfQoKcGFyKG1hcj1jKDUsNSwwLjUsMC41KSwgbWdwPWMoMywwLjcsMCkpCnNjZW5lKDE1MCkKYGBgCgpBY3R1YWwgbW92aWUKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoYW5pbWF0aW9uKQpzYXZlVmlkZW8oe3BhcihtYXI9Yyg2LDgsMSwxKSwgbWdwPWMoNS41LDAuNywwKSkKIGR1bW15IDwtIHBic2FwcGx5KHNlcSgwLCBieT0zLCBsZW49NTApLCBzY2VuZSwgY2V4PTIpOyBybShkdW1teSkKfSwgdmlkZW8ubmFtZT0iUW1vdmllLm1wNCIsIGludGVydmFsPTAuMSwgZmZtcGVnPSIvdXNyL2Jpbi9mZm1wZWciLCAKYW5pLndpZHRoPTcqMjAwLCBhbmkuaGVpZ2h0PTUqMjAwKQpgYGAKCltPcGVuIHZpZGVvIGluIGRlZmF1bHQgbXA0cGxheWVyXShRbW92aWUubXA0KQoKXApbdG9wXSgjdG9wKQoKIyBIeWRtb2QKSHlkcm9sb2dpY2FsIG1vZGVsbGluZyB3aXRoIGBhaXJHUmAgICAKKipLYXRpZSBTbWl0aCoqIChDZW50cmUgZm9yIEVjb2xvZ3kgJiBIeWRyb2xvZ3kpIDxrLmEuc21pdGhAY2VoLmFjLnVrPgoKVGhpcyBpcyBhbiBkZW1vbnN0cmF0aW9uIG9mIGhvdyB0byB1c2UgdGhlIGFpckdSIHBhY2thZ2Ugb2YgaHlkcm9sb2dpY2FsIG1vZGVscyBpbiBSLCBhcyB3ZWxsIGFzIGhvdyB0byBwbG90IGludGVyYWN0aXZlIHRpbWVzZXJpZXMgZ3JhcGhzIHdpdGggdGhlIGR5Z3JhcGhzIHBhY2thZ2UuCgpGaXJzdCB3ZSBuZWVkIHRvIGxvYWQgc29tZSBwYWNrYWdlcwpgYGB7cn0KbGlicmFyeSh4dHMpCmxpYnJhcnkoZHlncmFwaHMpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCiMjIyBEYXRhCgpOb3cgd2UnbGwgbG9hZCBpbiBzb21lIG9ic2VydmF0aW9uYWwgZmxvdyBkYXRhIGZyb20gdGhlIFJpdmVyIFRoYW1lcyAobmF0dXJhbGlzZWQpIGluIEVuZ2xhbmQgLSB3aXRoIHRoYW5rcyB0byB0aGUgTmF0aW9uYWwgUml2ZXIgRmxvdyBBcmNoaXZlOiBodHRwOi8vbnJmYS5jZWguYWMudWsvZGF0YS9zZWFyY2gKYGBge3J9Cm9ic2VydmVkX2RhdGEgPC0gcmVhZC5jc3YoImRhdGEvUW9ic18zOTAwMTAuY3N2IikKb2JzZXJ2ZWRfZGF0YQpvYnNlcnZlZF9kYXRhJERBVEUgPC0gc3RycHRpbWUob2JzZXJ2ZWRfZGF0YSREQVRFLCBmb3JtYXQgPSAiJWQvJW0vJVkiKQpgYGAKCkluIG9yZGVyIHRvIHBsb3QgdGhpcyBhcyBhbiBpbnRlcmFjdGl2ZSBkeWdyYXBoIHdlIG5lZWQgdG8gY2hhbmdlIGl0IHRvIHh0cyBmb3JtYXQKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQpvYnNlcnZlZF9kYXRhX3h0cyA8LSBhcy54dHMob2JzZXJ2ZWRfZGF0YSRRb2JzLCBvcmRlci5ieSA9IG9ic2VydmVkX2RhdGEkREFURSkKZHlncmFwaChvYnNlcnZlZF9kYXRhX3h0cywgbWFpbj0iTmF0dXJhbGlzZWQgU3RyZWFtZmxvdyBPYnNlcnZhdGlvbnMgZm9yIHRoZSBUaGFtZXMgYXQgS2luZ3N0b24iKSU+JQogIGR5QXhpcygieSIsIGxhYmVsPSJTdHJlYW1mbG93IChtMy9zIiklPiUKICBkeU9wdGlvbnMoY29sb3JzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDMsICJTZXQxIilbMl0pJT4lCiAgZHlSYW5nZVNlbGVjdG9yKCkKYGBgCk5vdyBsZXRzIHJlYWQgaW4gc29tZSBwcmVjaXBpdGF0aW9uIGRhdGEgLSB0aGlzIGlzIGZyb20gQ0VILUdFQVI6IGh0dHBzOi8vZGF0YS5nb3YudWsvZGF0YXNldC9ncmlkZGVkLWVzdGltYXRlcy1vZi1kYWlseS1hbmQtbW9udGhseS1hcmVhbC1yYWluZmFsbC1mb3ItdGhlLXVuaXRlZC1raW5nZG9tLTE4OTAtMjAxMi1jZWgtZ2VhcgoKYGBge3J9CnByZWNpcF9kYXRhIDwtIHJlYWQuY3N2KCJkYXRhL3JhaW5fMTk2MV8yMDE0XzM5MDAxMC5jc3YiKQpwcmVjaXBfZGF0YQpgYGAKYW5kIHNvbWUgcG90ZW50aWFsIGV2YXBvdHJhbnNwaXJhdGlvbiBkYXRhIC0gdGhpcyBpcyBmcm9tIENIRVNTLVBFOmh0dHBzOi8vZGF0YS5nb3YudWsvZGF0YXNldC9jbGltYXRlLWh5ZHJvbG9neS1hbmQtZWNvbG9neS1yZXNlYXJjaC1zdXBwb3J0LXN5c3RlbS1wb3RlbnRpYWwtZXZhcG90cmFuc3BpcmF0aW9uLWRhdGFzZXQtZm9yLTEKCmBgYHtyfQpQRVRfZGF0YSA8LSByZWFkLmNzdigiZGF0YS9DSEVTU19QRVRfMTk2MV8yMDE1XzM5MDAxMC5jc3YiKQpQRVRfZGF0YQpgYGAKCk5vdGUgdGhhdCBvdXIgc3RhcnRpbmcgZGF0ZXMgYXJlIG5vdCB0aGUgc2FtZSBhcyBvdXIgb2JzZXJ2YXRpb25hbCBkYXRhLCBzbyB3ZSBuZWVkIHRvIG1ha2UgYSBkYXRhZnJhbWUgdGhhdCBtYXRjaGVzIHRoZSBkYXRlcyB1cC4gVGhlcmUgYXJlIGEgbG90IG9mIHdheXMgdG8gZG8gdGhpcy4gRmlyc3Qgd2UnbGwgY29udmVydCB0aGVtIHRvIHRoZSBzYW1lIGRhdGUgZm9ybWF0LgpgYGB7cn0KcHJlY2lwX2RhdGEkREFURSA8LSBzdHJwdGltZShwcmVjaXBfZGF0YSREQVRFLCAiJVktJW0tJWQiKQpQRVRfZGF0YSREQVRFIDwtIHN0cnB0aW1lKFBFVF9kYXRhJERBVEUsICIlWS0lbS0lZCIpCmBgYApub3cgd2UnbGwgZmluZCB0aGUgY29tbW9uIHBlcmlvZApgYGB7cn0KZmlyc3RfZGF0ZSA8LSBtYXgob2JzZXJ2ZWRfZGF0YSREQVRFWzFdLCBwcmVjaXBfZGF0YSREQVRFWzFdLCBQRVRfZGF0YSREQVRFWzFdKQpsYXN0X2RhdGUgPC0gbWluKG9ic2VydmVkX2RhdGEkREFURVtsZW5ndGgob2JzZXJ2ZWRfZGF0YSREQVRFKV0sIHByZWNpcF9kYXRhJERBVEVbbGVuZ3RoKHByZWNpcF9kYXRhJERBVEUpXSwgUEVUX2RhdGEkREFURVtsZW5ndGgoUEVUX2RhdGEkREFURSldKQpgYGAKYW5kIG1ha2UgYSBkYXRhIGZyYW1lIG9mIHRoYXQgbGVuZ3RoCmBgYHtyfQojIG1ha2UgYW4gZW1wdHkgZGF0YSBmcmFtZQp0aGFtZXNfZGF0YSA8LSBhcy5kYXRhLmZyYW1lKG1hdHJpeChOQSxucm93PWFzLm51bWVyaWMoKGxhc3RfZGF0ZS1maXJzdF9kYXRlKSsxKSwgbmNvbD00KSkKY29sbmFtZXModGhhbWVzX2RhdGEpIDwtYyAoImRhdGUiLCJQRVQiLCJwcmVjaXAiLCJvYnMiKQojIG1ha2UgdGhlIGRhdGUgdGltZXNlcmllcwp0aGFtZXNfZGF0YSRkYXRlIDwtIHNlcShmaXJzdF9kYXRlLCBsYXN0X2RhdGUsIGJ5PSJkYXlzIikKIyBwb3B1bGF0ZSB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBkYXRhCnRoYW1lc19kYXRhJG9icyA8LSBvYnNlcnZlZF9kYXRhJFFvYnNbd2hpY2gob2JzZXJ2ZWRfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlWzFdKTp3aGljaChvYnNlcnZlZF9kYXRhJERBVEU9PXRoYW1lc19kYXRhJGRhdGVbbGVuZ3RoKHRoYW1lc19kYXRhJGRhdGUpXSldCnRoYW1lc19kYXRhJHByZWNpcCA8LSBwcmVjaXBfZGF0YSRNZWFuX3JhaW5mYWxsW3doaWNoKHByZWNpcF9kYXRhJERBVEU9PXRoYW1lc19kYXRhJGRhdGVbMV0pOndoaWNoKHByZWNpcF9kYXRhJERBVEU9PXRoYW1lc19kYXRhJGRhdGVbbGVuZ3RoKHRoYW1lc19kYXRhJGRhdGUpXSldCnRoYW1lc19kYXRhJFBFVCA8LSBQRVRfZGF0YSRQRVRbd2hpY2goUEVUX2RhdGEkREFURT09dGhhbWVzX2RhdGEkZGF0ZVsxXSk6d2hpY2goUEVUX2RhdGEkREFURT09dGhhbWVzX2RhdGEkZGF0ZVtsZW5ndGgodGhhbWVzX2RhdGEkZGF0ZSldKV0KYGBgCgojIyMgSW50ZXJhY3RpdmUgdGltZSBzZXJpZXMgcGxvdAoKcGxvdCB0aGUgb2JzZXJ2ZWQgc3RyZWFtZmxvdyB3aXRoIHRoZSBwcmVjaXBpdGF0aW9uIGRhdGEKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQojICBjb252ZXJ0IHRoZSBvYnNlcnZlZCBkaXNjaGFyZ2UgdG8gcnVub2ZmIChzbyBpdHMgaW4gdGhlIHNhbWUgdW5pdHMgYXMgdGhlIHByZWNpcCkKIyBkaXZpZGUgYnkgY2F0Y2htZW50IGFyZWEgKG0yKSBhbmQgbXVsaXRwbHkgYnkgODYuNAp0aGFtZXNfZGF0YSRvYnMgPC0gKHRoYW1lc19kYXRhJG9icy85OTQ4LjApKjg2LjQKdGhhbWVzX2RhdGFfeHRzIDwtIGFzLnh0cyh0aGFtZXNfZGF0YVssMzo0XSwgb3JkZXIuYnk9dGhhbWVzX2RhdGEkZGF0ZSkKIyBpbml0aWF0ZSB0aGUgZHlncmFwaApkeWdyYXBoKHRoYW1lc19kYXRhX3h0cyklPiUKIyBkZWZpbmUgdGhlIGZpcnN0IGF4aXMgIApkeUF4aXMoIm9icyIsIG5hbWUgPSAieSIsIGxhYmVsID0gInJ1bm9mZiAobW0vZGF5KSIsCiAgICAgICB2YWx1ZVJhbmdlID0gcmFuZ2UodGhhbWVzX2RhdGFfeHRzWywgIm9icyJdLAogICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSkqIGMoMC4wMSwgMS41OSkpJT4lCiMgZGVmaW5lIHRoZSBzZWNvbmQgYXhpcwpkeUF4aXMoInByZWNpcCIsIG5hbWUgPSAieTIiLCBsYWJlbCA9ICJwcmVjaXAgKG1tL2RheSkiLAogICAgICAgICAgICAgICAgICAgdmFsdWVSYW5nZSA9IHJldihyYW5nZSh0aGFtZXNfZGF0YV94dHNbLCAicHJlY2lwIl0sIAogICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFKSogYygwLjAxLCAyLjk5KSkpJT4lCiMgcGxvdCB0aGUgZGF0YQpkeVNlcmllcygib2JzIixheGlzID0gJ3knKSU+JQpkeVNlcmllcygicHJlY2lwIiwgYXhpcyA9ICd5MicsIHN0ZXBQbG90ID0gVFJVRSwKICAgICAgICAgZmlsbEdyYXBoID0gVFJVRSklPiUKZHlPcHRpb25zKGNvbG9ycyA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCgzLCAiU2V0MSIpWzI6M10pJT4lCmR5UmFuZ2VTZWxlY3RvcigpCmBgYAoKIyMjIEh5ZHJvbG9naWNhbCBtb2RlbGluZwoKT0ssIGVub3VnaCBtZXNzaW5nIHdpdGggZGF0YSwgbGV0cyBkbyBzb21lIG1vZGVsbGluZwoKc2VlIHRoaXMgd2Vic2l0ZSBmb3IgYSBnb29kIGd1aWRlIHRocm91Z2ggdGhlIG1vZGVsOiAKPGh0dHBzOi8vb2RlbGFpZ3VlLmdpdGh1Yi5pby9haXJHUi90dXRvcmlhbF8xX2dldHRpbmdfc3RhcnRlZC5odG1sPgoKbG9hZCB0aGUgR1IgcGFja2FnZQpgYGB7cn0KcmVxdWlyZShhaXJHUiwgcXVpZXRseT1UUlVFKQpgYGAKCnByZXBhcmUgdGhlIGlucHV0IGRhdGEgaW4gdGhlIGNvcnJlY3QgZm9ybWF0CmBgYHtyfQpCYXNpbk9icyA8LSB0aGFtZXNfZGF0YQpjb2xuYW1lcyhCYXNpbk9icykgPC0gYygnRGF0ZXNSJywnRScsJ1AnLCAnUW9icycpCmBgYAoKY3JlYXRlIHRoZSBJbnB1dHNNb2RlbCBvYmplY3QgLSB0aGlzIGRlZmluZXMgd2hpY2ggbW9kZWwgd2Ugd2FudCB0byBydW4sIGFuZCBkZWZpbmVzIHRoZSB2YXJpYWJsZXMgZm9yIHRoZSBtb2RlbHMgaW5wdXQgZGF0YQoKYGBge3J9CklucHV0c01vZGVsIDwtIENyZWF0ZUlucHV0c01vZGVsKEZVTl9NT0QgPSBSdW5Nb2RlbF9HUjRKLERhdGVzUiA9IEJhc2luT2JzJERhdGVzUiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaXAgPSBCYXNpbk9icyRQLFBvdEV2YXAgPSBCYXNpbk9icyRFKQpzdHIoSW5wdXRzTW9kZWwpCiMgbm90ZSBOQSB2YWx1ZXMgb2YgcHJlY2lwIGFuZCBQRVQgYXJlIE5PVCBBTExPV0VECmBgYAoKY3JlYXRlIHRoZSBSdW5PcHRpb25zIG9iamVjdCAtIHRoaXMgZGVmaW5lcyBvcHRpb25zIGZvciB0aGUgUnVuTW9kZWxfR1I0SiBmdW5jdGlvbgpgYGB7cn0KIyBmaXJzdCBkZWZpbmUgdGhlIHBlcmlvZCB0byBydW4gdGhlIG1vZGVsIG92ZXIKSW5kX1J1biA8LSBzZXEod2hpY2goQmFzaW5PYnMkRGF0ZXNSPT0iMTk4MS0wMS0wMSIpLAogICAgICAgICAgICAgd2hpY2goQmFzaW5PYnMkRGF0ZXNSPT0iMjAxNC0xMi0zMSIpKQpSdW5PcHRpb25zIDwtIENyZWF0ZVJ1bk9wdGlvbnMoRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5kUGVyaW9kX1J1biA9IEluZF9SdW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfV2FybVVwID0gTlVMTCkKc3RyKFJ1bk9wdGlvbnMpCmBgYAoKY3JlYXRlIHRoZSBJbnB1dHNDcml0IG9iamVjdCAtIGRlZmluZSB0aGUgZXJyb3IgbWV0cmljIChjaG9vc2UgZnJvbSBSTVNFLCBOU0UsIEtHRSBvciBtb2RpZmllZCBLR0UgKEtHRTIpKQpgYGB7cn0KSW5wdXRzQ3JpdCA8LSBDcmVhdGVJbnB1dHNDcml0KEZVTl9DUklUID0gRXJyb3JDcml0X05TRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFFvYnMgPSBCYXNpbk9icyRRb2JzW0luZF9SdW5dKQpzdHIoSW5wdXRzQ3JpdCkKYGBgCgpjcmVhdGUgdGhlIENhbGliT3B0aW9ucyBvYmplY3QgLSBjaG9vc2UgdGhlIGNhbGlicmF0aW9uIGFsZ29yaXRobQpgYGB7cn0KQ2FsaWJPcHRpb25zIDwtIENyZWF0ZUNhbGliT3B0aW9ucyhGVU5fTU9EID0gUnVuTW9kZWxfR1I0SiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU5fQ0FMSUIgPSBDYWxpYnJhdGlvbl9NaWNoZWwpCnN0cihDYWxpYk9wdGlvbnMpCmBgYAoKcnVuIHRoZSBjYWxpYnJhdGlvbgpgYGB7cn0KT3V0cHV0c0NhbGliIDwtIENhbGlicmF0aW9uX01pY2hlbChJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNDcml0ID0gSW5wdXRzQ3JpdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYWxpYk9wdGlvbnMgPSBDYWxpYk9wdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX0NSSVQgPSBFcnJvckNyaXRfTlNFKQpgYGAKCk5TRSBvZiAwLjkyNDYgLSBub3QgYmFkIGF0IGFsbCEKCmRlZmluZSB0aGUgcGFyYW1ldGVycyBmb3VuZCBieSB0aGUgY2FsaWJyYXRpb24gcm91dGluZQpgYGB7cn0KUGFyYW0gPC0gT3V0cHV0c0NhbGliJFBhcmFtRmluYWxSClBhcmFtCmBgYAoKKipSVU4gVEhFIE1PREVMISoqCmBgYHtyfQpPdXRwdXRzTW9kZWwgPC0gUnVuTW9kZWxfR1I0SihJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhcmFtPSBQYXJhbSkKc3RyKE91dHB1dHNNb2RlbCkKYGBgCgp1c2UgdGhlIGluYnVpbHQgcGxvdCBmdW5jdGlvbiB0byBsb29rIGF0IHRoZSByZXN1bHRzCmBgYHtyfQpwbG90KE91dHB1dHNNb2RlbCwgUW9icz1CYXNpbk9icyRRb2JzW0luZF9SdW5dKQpgYGAKCmxvb2tpbmcgZ29vZCAtIGJ1dCB3ZSd2ZSBnb3Qgc29tZSBkaXNjcmVwYW5jeSBhdCB0aGUgbG93IGZsb3dzIGVuZC4gTlNFIGlzIG5vdG9yaW91cyBmb3IgdGhpcywgaXQgaXMgYmFzZWQgb24gdGhlIHNxdWFyZSBvZiB0aGUgZmxvd3MsIHNvIG92ZXItd2VpZ2h0cyB0aGUgY2FsaWJyYXRpb24gdG8gdGhlIGhpZ2ggZmxvd3MuIEkgd29uZGVyIGlmIHRoZSBtb2RpZmllZCBLR0UgY2FuIGRvIGFueSBiZXR0ZXI/CmBgYHtyfQojIG1ha2UgYSBmZXcgY2hhbmdlcyB0byB0aGUgY2FsaWJyYXRpb24gY3JpdGVyaWEKSW5wdXRzQ3JpdCA8LSBDcmVhdGVJbnB1dHNDcml0KEZVTl9DUklUID0gRXJyb3JDcml0X0tHRTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUnVuT3B0aW9ucyA9IFJ1bk9wdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBRb2JzID0gQmFzaW5PYnMkUW9ic1tJbmRfUnVuXSkKIyByZXJ1biB0aGUgY2FsaWJyYXRpb24KT3V0cHV0c0NhbGliIDwtIENhbGlicmF0aW9uX01pY2hlbChJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNDcml0ID0gSW5wdXRzQ3JpdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDYWxpYk9wdGlvbnMgPSBDYWxpYk9wdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX0NSSVQgPSBFcnJvckNyaXRfS0dFMikKIyByZWRlZmluZSB0aGUgcGFyYW1ldGVycwpQYXJhbSA8LSBPdXRwdXRzQ2FsaWIkUGFyYW1GaW5hbFIKIyByZXJ1biB0aGUgbW9kZWwKT3V0cHV0c01vZGVsIDwtIFJ1bk1vZGVsX0dSNEooSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXJhbT0gUGFyYW0pCiMgcGxvdCBhZ2FpbgpwbG90KE91dHB1dHNNb2RlbCwgUW9icz1CYXNpbk9icyRRb2JzW0luZF9SdW5dKQpgYGAKCm5vdCBtdWNoIGRpZmZlcmVudC4gT2ggd2VsbCwgd2UgY2FuIGJlIGhhcHB5IHdpdGggZWl0aGVyIG9mIHRob3NlIG1ldHJpYyBzY29yZXMuIC0gcGF1c2UgZm9yIHRob3VnaHQgLSB3aGljaCBwYXJhbWV0ZXIgc2V0IHdvdWxkIHlvdSBjaG9vc2UgdG8gdXNlPyEKCiMjIyBWYWxpZGF0aW9uCgpMZXQncyBkbyBzb21lIHZhbGlkYXRpb24KYGBge3J9CiMgZ28gYmFjayB0byB0aGUgYmVnaW5uaW5nLCByZWRlZmluZSB0aGUgcGVyaW9kIHRvIHJ1biBvbiAodGhlIHBlcmlvZCB3ZSBoYXZlbid0IHVzZWQgZm9yIGNhbGlicmF0aW9uLCBtaW51cyB0aGUgZmlyc3QgeWVhciBuZWVkZWQgZm9yIHdhcm0gdXApCkluZF9SdW4gPC0gc2VxKHdoaWNoKEJhc2luT2JzJERhdGVzUj09IjE5NjItMDEtMDEiKSwKICAgICAgICAgICAgIHdoaWNoKEJhc2luT2JzJERhdGVzUj09IjE5ODAtMTItMzEiKSkKUnVuT3B0aW9ucyA8LSBDcmVhdGVSdW5PcHRpb25zKEZVTl9NT0QgPSBSdW5Nb2RlbF9HUjRKLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEluZFBlcmlvZF9SdW4gPSBJbmRfUnVuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5kUGVyaW9kX1dhcm1VcCA9IE5VTEwpCklucHV0c0NyaXQgPC0gQ3JlYXRlSW5wdXRzQ3JpdChGVU5fQ1JJVCA9IEVycm9yQ3JpdF9LR0UyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUW9icyA9IEJhc2luT2JzJFFvYnNbSW5kX1J1bl0pClBhcmFtIDwtIE91dHB1dHNDYWxpYiRQYXJhbUZpbmFsUgpPdXRwdXRzTW9kZWwgPC0gUnVuTW9kZWxfR1I0SihJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhcmFtPSBQYXJhbSkKT3V0cHV0c0NyaXQgPC0gRXJyb3JDcml0X0tHRTIoSW5wdXRzQ3JpdCA9IElucHV0c0NyaXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdXRwdXRzTW9kZWwgPSBPdXRwdXRzTW9kZWwpCmBgYAoKc2xpZ2h0bHkgd29yc2UgdGhhbiB0aGUgY2FsaWJyYXRpb24gcGVyaW9kICgwLjk2MjEpIGJ1dCBub3QgYmFkIGF0IGFsbAoKZmluYWxseSwgbGV0J3MgcnVuIHRoZSBtb2RlbCBmb3IgdGhlIHdob2xlIHRpbWUgcGVyaW9kIGFuZCBwbG90IGEgZHlncmFwaCBzbyB3ZSBjYW4gbG9vayBhdCB0aGUgdGltZXNlcmllcyBpbiBtb3JlIGRldGFpbApgYGB7cn0KSW5kX1J1biA8LSBzZXEod2hpY2goQmFzaW5PYnMkRGF0ZXNSPT0iMTk2Mi0wMS0wMSIpLAogICAgICAgICAgICAgd2hpY2goQmFzaW5PYnMkRGF0ZXNSPT0iMjAxNC0xMi0zMSIpKQpSdW5PcHRpb25zIDwtIENyZWF0ZVJ1bk9wdGlvbnMoRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5kUGVyaW9kX1J1biA9IEluZF9SdW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfV2FybVVwID0gTlVMTCkKUGFyYW0gPC0gT3V0cHV0c0NhbGliJFBhcmFtRmluYWxSCk91dHB1dHNNb2RlbCA8LSBSdW5Nb2RlbF9HUjRKKElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUnVuT3B0aW9ucyA9IFJ1bk9wdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFyYW09IFBhcmFtKQpwbG90X291dHB1dF9kYXRhIDwtIGFzLmRhdGEuZnJhbWUobWF0cml4KE5BLCBuY29sID0gMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm93ID0gbGVuZ3RoKE91dHB1dHNNb2RlbCREYXRlc1IpKSkKY29sbmFtZXMocGxvdF9vdXRwdXRfZGF0YSkgPC0gYygiRGF0ZSIsICJRc2ltIiwgIlFvYnMiKQpwbG90X291dHB1dF9kYXRhJERhdGUgPC0gT3V0cHV0c01vZGVsJERhdGVzUgpwbG90X291dHB1dF9kYXRhJFFzaW0gPC0gT3V0cHV0c01vZGVsJFFzaW0KcGxvdF9vdXRwdXRfZGF0YSRRb2JzIDwtIEJhc2luT2JzJFFvYnNbSW5kX1J1bl0KcGxvdF9vdXRwdXRfZGF0YV94dHMgPC0gYXMueHRzKHBsb3Rfb3V0cHV0X2RhdGEsIG9yZGVyLmJ5ID0gcGxvdF9vdXRwdXRfZGF0YSREYXRlKQpkeWdyYXBoKHBsb3Rfb3V0cHV0X2RhdGFfeHRzLCBtYWluPSJPYnNlcnZlZCBhbmQgU2ltdWxhdGVkIFJ1bm9mZiBmb3IgdGhlIFRoYW1lcyBhdCBLaW5nc3RvbiAoTmF0dXJhbGlzZWQpIiklPiUKICBkeU9wdGlvbnMoY29sb3JzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDMsIlNldDEiKVsyOjFdKSU+JQogIGR5QXhpcygieSIsIGxhYmVsPSJSdW5vZmYgKG1tL2RheSkiKSU+JQogIGR5UmFuZ2VTZWxlY3RvcigpCmBgYAoKVGhhbmtzIGZvciBsaXN0ZW5pbmchIEhvcGUgeW91IGdldCB0byB0cnkgaXQgb3V0LgoKQW55IGdlbmVyYWwgcXVlc3Rpb25zPyBQbGVhc2UgZmVlbCBmcmVlIHRvIHBvc3QgdGhlbSBvbiBmYWNlYm9vayBwYWdlOiBodHRwczovL3d3dy5mYWNlYm9vay5jb20vZ3JvdXBzLzExMzAyMTQ3NzcxMjM5MDkvP3JlZj1ib29rbWFya3MKCkdSIHNwZWNpZmljIHF1ZXN0aW9ucz8gRW1haWwgPGFpckdSQGlyc3RlYS5mcj4KClwKW3RvcF0oI3RvcCkKCiMgRURBCkV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgaW5jbHVkaW5nIGZsb3cgZHVyYXRpb24gY3VydmUgYW5kIHRyZW5kIGFuYWx5c2lzIG9uIHRpbWUtc2VyaWVzICAgCioqU2hhdW4gSGFycmlnYW4qKgoKXApbdG9wXSgjdG9wKQoKCiMgRGlzY3Vzc2lvbgoKUGxlYXNlIGdpdmUgdXMgZmVlZGJhY2sgYXQKPGZvbnQgc2l6ZT0iNiI+W2JpdC5seS9mZWVkYmFja1JdKGh0dHBzOi8vYml0Lmx5L2ZlZWRiYWNrUik8L2ZvbnQ+IAoKRm9yIGRpc2N1c3Npb25zLCBwbGVhc2UgdXNlIHRoZSAKW0h5ZHJvbG9neSBpbiBSIEZhY2Vib29rIGdyb3VwXShodHRwczovL3d3dy5mYWNlYm9vay5jb20vZ3JvdXBzLzExMzAyMTQ3NzcxMjM5MDkvKS4gIAoK